package game.game;

import com.crunch.core.Renderable;
import com.crunch.graphics.gpu.GpuState;
import com.crunch.graphics.gpu.GpuTexture;
import com.crunch.math.MathOps;
import com.crunch.math.Vector2f;
import com.crunch.physics.Capsule;
import com.crunch.physics.Contact;
import com.crunch.physics.DynamicPhysicsEntity;
import org.json.JSONException;
import org.json.JSONObject;

import java.awt.event.KeyEvent;

/**
 * Player class.
 */
public class Player extends GameActor implements DynamicPhysicsEntity.EventHandler, Renderable {
	public static final int UPDATE_PRIORITY = 0;
	public static final int RENDER_DEPTH    = 0;

	public Player() {
		super(UPDATE_PRIORITY);
	}

	public void onUpdate() {
		// TODO this should be post-physics simulation
		Vector2f pos = physicsEntity.getPosition();
		cameraController.updateTarget(
				Math.round(pos.x() * RoomState.PHYSICS_SCALE),
				Math.round(pos.y() * RoomState.PHYSICS_SCALE));
	}

	@Override
	public void onGameActorDestroy() {
		if (physicsEntity != null) {
			physicsEntity.remove();
		}

		if (texture != null) {
			texture.release();
		}
		getEngine().window.renderer.unregister(this);
	}

	@Override
	public void initState(JSONObject state) throws JSONException {
		RoomState roomState = getRoomState();

		getEngine().window.renderer.register(this);

		float x = (float) state.getDouble("x");
		float y = (float) state.getDouble("y");

		DynamicPhysicsEntity.Description desc = new DynamicPhysicsEntity.Description();
		desc.shape = new Capsule(1.0f, 0.5f);
		desc.rotation = 0.0f;
		desc.category = 0;
		desc.initialPosition = new Vector2f(5.5f, 5.0f);
		desc.initialVelocity = new Vector2f();
		desc.mass = 1.0f;
		desc.sticksToFloor = true;
		desc.floorNormal = new Vector2f(0.0f, 1.0f);
		desc.maxFloorAngle = (float) Math.PI / 4.0f;
		desc.sticksToFloorWhenPushed = true;
		desc.repelAcceleration = 1.0f;
		desc.data = this;
		desc.eventHandler = this;
		physicsEntity = roomState.getPhysicsSimulation().addDynamicEntity(desc);

		cameraController = new CameraController(roomState.getCamera(),
				roomState.getRoomWidth(), roomState.getRoomHeight(),
				new CameraController.Settings());
		cameraController.initTarget(
				Math.round(x * RoomState.PHYSICS_SCALE),
				Math.round(y * RoomState.PHYSICS_SCALE));
	}

	@Override
	public void loadState(JSONObject state) throws JSONException {
		// ...
	}

	@Override
	public JSONObject saveState() throws JSONException {
		// ...
		return null;
	}

	public DynamicPhysicsEntity getPhysicsEntity() {
		return physicsEntity;
	}

	@Override
	public void onPreStep(DynamicPhysicsEntity entity) {}

	@Override
	public void onPostStep(DynamicPhysicsEntity entity) {}

	@Override
	public void onProcessInput(DynamicPhysicsEntity entity, float dt) {
		final float GRAVITY = 32.0f;
		final float FRICTION = 40.0f;
		final float AIR_FRICTION = 10.0f;
		final float MAX_WALK_SPEED = 10.0f;
		final float JUMP_SPEED = 12.0f;

		float walkDirection = 0.0f;
		float jump = 0.0f;
		if (getEngine().input.isKeyDown(KeyEvent.VK_D)) {
			walkDirection += 1.0f;
		}
		if (getEngine().input.isKeyDown(KeyEvent.VK_A)) {
			walkDirection -= 1.0f;
		}
		if (getEngine().input.isKeyDown(KeyEvent.VK_W)) {
			jump = JUMP_SPEED;
		}

		Vector2f vel = entity.getVelocity();
		if (entity.getFloorContact() == null) {
			// accelerate downward due to gravity
			vel.y(vel.y() - GRAVITY * dt);

			// we can move from side to side in the air, but slower than on the ground
			float newVelX = vel.x() + walkDirection*AIR_FRICTION*dt;
			if (vel.x() < MAX_WALK_SPEED && newVelX > vel.x()) {
				vel.x(Math.min(MAX_WALK_SPEED, newVelX));
			} else if (vel.x() > -MAX_WALK_SPEED && newVelX < vel.x()) {
				vel.x(Math.max(-MAX_WALK_SPEED, newVelX));
			}
		} else {
			Contact floorContact = entity.getFloorContact();
			Vector2f floorVelocity = floorContact.getVelocity();
			vel.subtractAssign(floorVelocity);
			if (walkDirection == 0.0f) {
				// slow down velocity
				if (!vel.isZero()) {
					float mag = vel.magnitude();
					float newMag = Math.max(mag - FRICTION * dt, 0.0f);
					vel.multiplyAssign(newMag / mag);
				}
			} else {
				// find velocity relative to floor and renormalize
				// n.p = 0; we have px, so solve for py
				// py = -px*nx/ny
				Vector2f n = floorContact.getNormal();
				float py = -walkDirection * n.x() / n.y();
				Vector2f floorVel = new Vector2f(walkDirection, py);

				Vector2f tarVel = floorVel.normalize().multiplyAssign(MAX_WALK_SPEED);

				// move at speed friction allows for
				Vector2f diffFromOldVel = tarVel.subtract(vel);
				float diffMag = diffFromOldVel.magnitude();
				if (diffMag != 0.0f) {
					// clamp to 1 so we don't move past the target point
					float lerpAmt = Math.min((FRICTION * dt) / diffMag, 1.0f);
					vel.set(MathOps.lerp(vel, tarVel, lerpAmt));
				}

				// to make sure we stay in contact with the ground, add a little bit of force downward
				vel.subtractAssign(n.multiply(0.001f));
			}

			vel.y(vel.y() + jump);
			vel.addAssign(floorVelocity);
		}

		// TEMP
		if (getEngine().input.isKeyDown(KeyEvent.VK_R)) {
			entity.setPosition(new Vector2f(5.5f, 5.0f));
			vel.setZero();
		}

		entity.setVelocity(vel);
	}

	@Override
	public void render(GpuState gpuState) {
		//
	}

	@Override
	public int getDepth() {
		return RENDER_DEPTH;
	}

	private GpuTexture texture;

	private CameraController cameraController;
	private DynamicPhysicsEntity physicsEntity;
}
